﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace CutAways.Modeling
{
	class ObjLoader
	{
		private int lineNumber = 0;

		public Model LoadFromFile(string filename)
		{
			if (filename == null || !File.Exists(filename))
				throw new ModelLoadingException("Obj file can't be found.");
			
			Model model = new Model();

			model.Name = Path.GetFileNameWithoutExtension(filename);

			List<Vertex> vertices = new List<Vertex>();
			List<Vertex> normals = new List<Vertex>();
			List<Vertex2D> textureCoordinates = new List<Vertex2D>();
			List<Triangle> triangles = new List<Triangle>();

			List<GeometricObject> objects = new List<GeometricObject>();

			TextReader input = File.OpenText(filename);

			try
			{
				this.lineNumber = 1;
				GeometricObject currentObject = null;
				int objectCounter = 0;
                int currentMaterialIndex = -1;
				for (string line = input.ReadLine(); line != null; line = input.ReadLine(), lineNumber++)
				{
					line = line.Trim();
					if (line == string.Empty || line[0] == '#') // komentar nebo prazdnej radek
						continue;
					string[] parts = line.Split(' ');


					switch (parts[0])
					{
						case "mtllib": // soubor s materialy
							model.Materials = new MaterialLoader().LoadFromFile(Path.IsPathRooted(parts[1]) ? parts[1] : Path.Combine(Path.GetDirectoryName(filename), parts[1]));
							break;
                        case "usemtl": // use material
                            currentMaterialIndex = getIndexOfMaterialWithName(line.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries)[1], model.Materials);
                            break;
						case "g": // novy objekt
							addObject(currentObject, objects, triangles);
							currentObject = new GeometricObject(objectCounter++);
							currentObject.Name = parts.Length > 1 ? line.Substring(line.IndexOf(' ') + 1) : "";
							break;
						case "v": // vertex
							vertices.Add(new Vertex(float.Parse(parts[1]), float.Parse(parts[2]), float.Parse(parts[3])));
							break;
						case "vt": // texture coordinate
							textureCoordinates.Add(new Vertex2D(float.Parse(parts[1]), float.Parse(parts[2])));
							break;
						case "vn": // normal
							normals.Add(new Vertex(float.Parse(parts[1]), float.Parse(parts[2]), float.Parse(parts[3])));
							break;
						case "f": // face
							triangles.AddRange(parseTrianglesFromPolygon(parts, currentMaterialIndex));
							break;
					}
				}
				addObject(currentObject, objects, triangles);
			}
			finally
			{
				input.Close();
			}

			model.Vertices = vertices.ToArray();
			model.TextureCoordinates = textureCoordinates.ToArray();
			model.Normals = normals.ToArray();

			model.Objects = objects.ToArray();

			model.Normalize();

			return model;
		}

        private int getIndexOfMaterialWithName(string materialName, Material[] materials)
        {
            for (int i = 0; i < materials.Length;i++ )
            {
                if (materialName == materials[i].Name)
                    return i;
            }
            return -1;
        }

		private IEnumerable<Triangle> parseTrianglesFromPolygon(string[] parts, int currentMaterialIndex)
		{
			if (parts.Length < 4)
				throw new ModelLoadingException("Face must have at least three vertices.", this.lineNumber);
			List<Triangle> triangles = new List<Triangle>();

			Triangle newTriangle = new Triangle(currentMaterialIndex);
			fillTriangleVertexData(newTriangle, 0, parts[1]);
			fillTriangleVertexData(newTriangle, 1, parts[2]);
			fillTriangleVertexData(newTriangle, 2, parts[3]);
			triangles.Add(newTriangle);
			int firstVertex = 1, lastVertex = 3;
			for (int i = 4; i < parts.Length; i++)
			{
				newTriangle = new Triangle(currentMaterialIndex);
				fillTriangleVertexData(newTriangle, 0, parts[firstVertex]);
				fillTriangleVertexData(newTriangle, 1, parts[lastVertex]);
				fillTriangleVertexData(newTriangle, 2, parts[i]);
				lastVertex = i;
				triangles.Add(newTriangle);
			}
			return triangles;
		}

		private void fillTriangleVertexData(Triangle t, int vertexIndex, string data)
		{
			string[] numbers = data.Split('/');
			if (numbers.Length > 0 && !string.IsNullOrEmpty(numbers[0]))
			{
				t.VertexIndices[vertexIndex] = int.Parse(numbers[0]) - 1;
				if (numbers.Length > 1 && !string.IsNullOrEmpty(numbers[1]))
				{
					t.TextureCoordinateIndices[vertexIndex] = int.Parse(numbers[1]) - 1;
					if (numbers.Length > 2 && !string.IsNullOrEmpty(numbers[1]))
						t.NormalIndices[vertexIndex] = int.Parse(numbers[2]) - 1;
				}

			}
		}

		private void addObject(GeometricObject obj, List<GeometricObject> objects, List<Triangle> triangles)
		{
			if (obj != null)
			{
				obj.Triangles = triangles.ToArray();
				triangles.Clear();
				objects.Add(obj);
			}
		}
	}
}
